package yartiss.engine.simulation.system.schedulable;

import java.util.ArrayList;
import java.util.concurrent.CopyOnWriteArrayList;

import yartiss.engine.simulation.IEnergyConsumptionProfile;
import yartiss.engine.simulation.io.ITaskElement;
import yartiss.engine.simulation.io.ITaskElementVisitor;
import yartiss.engine.simulation.system.ITask;
import yartiss.engine.simulation.system.PeriodicTask;

/**
 * Real-time task of graph model
 * 
 * @author Manar Qamhieh
 * 
 */
public class GraphTask extends PeriodicTask implements ITaskElement {
	private ArrayList<SubTask> subtasks;

	public GraphTask(long firstrelease, long wcee, long period, long deadline,
			int priority, ArrayList<SubTask> subtasks,
			IEnergyConsumptionProfile p) {
		super(firstrelease, getTotolWcet(subtasks), wcee, period, deadline,
				priority, p);
		this.subtasks = subtasks;
	}

	public GraphTask(GraphTask graphTask) {
		this(graphTask.getFirstRelease(), graphTask.getWcee(), graphTask
				.getPeriod(), graphTask.getDeadline(), graphTask.getPriority(),
				graphTask.getSubtasks(), graphTask
						.getEnergyConsumptionProfile());
	}

	/**
	 * Get the total execution time of a graph, which is the sum of all its
	 * subtasks' wcets
	 * 
	 * @param subtasks
	 *            subtasks of the graph
	 * @return total wcet of a graph
	 */
	private static long getTotolWcet(ArrayList<SubTask> subtasks) {
		long wcets = 0;
		for (SubTask subTask : subtasks) {
			wcets += subTask.getWcet();
		}
		return wcets;
	}

	/**
	 * Gets the subtasks of the graph
	 * 
	 * @return subtasks of a graph
	 */
	public ArrayList<SubTask> getSubtasks() {
		return subtasks;
	}

	/**
	 * Gets the starting subtask of a graph which is the subtask with no parent
	 * subtasks graph has one starting subtask
	 * 
	 * @return starting subtask of a graph
	 */
	public SubTask getStartingSubtask() {
		for (SubTask subtask : subtasks) {
			if (subtask.getParentTasks().isEmpty())
				return subtask;
		}
		return null;
	}

	/**
	 * Gets the ending subtask of a graph which is the subtask with no children
	 * subtasks graph has one ending subtask
	 * 
	 * @return ending subtask of a graph
	 */
	public SubTask getEndingSubtask() {
		for (SubTask subtask : subtasks) {
			if (subtask.getChildrenTasks().isEmpty())
				return subtask;
		}
		return null;
	}

	/**
	 * Gets the slack time of the graph, which is equal to the latest or
	 * earliest finish time of the ending task after calculating the critical
	 * path of the graph.
	 * 
	 * @return slack time of the graph
	 */
	public long getSlack() {
		if (getEndingSubtask().getLatestFinisheTime() == 0)
			calculateCriticalPath();
		return getEndingSubtask().getEarliestFinishTime();
	}

	/**
	 * Removes a subtask from the graph and its edges
	 * 
	 * @param subtask
	 *            subtask to be removed
	 */
	public void removeTask(SubTask subtask) {
		for (SubTask child : subtask.getChildrenTasks()) {
			removeEdge(subtask, child);
		}
		for (SubTask parent : subtask.getParentTasks()) {
			removeEdge(parent, subtask);
		}
		subtasks.remove(subtask);
	}

	/**
	 * Adds a subtask to the graph
	 * 
	 * @param subtask
	 *            subtask to be added
	 */
	public void addTask(SubTask subtask) {
		subtask.setIndex(subtasks);
		subtasks.add(subtask);
	}

	/**
	 * Adds a directed edge between 2 subtasks in the graph
	 * 
	 * @param parent
	 *            parent subtask
	 * @param child
	 *            child subtask
	 */
	public void addEdge(SubTask parent, SubTask child) {
		if (!parent.getChildrenTasks().contains(child))
			parent.getChildrenTasks().add(child);
		if (!child.getParentTasks().contains(parent))
			child.getParentTasks().add(parent);
	}

	/**
	 * Removes a directed edge between 2 subtasks in the graph
	 * 
	 * @param parent
	 *            parent subtask
	 * @param child
	 *            child subtask
	 */
	public void removeEdge(SubTask parent, SubTask child) {
		if (parent.getChildrenTasks().contains(child))
			parent.getChildrenTasks().remove(child);
		if (child.getParentTasks().contains(parent))
			child.getParentTasks().remove(parent);
	}

	/**
	 * Verify if an edge can be created between 2 subtasks in a graph an edge
	 * can't be created if 2 subtasks share the same parent
	 * 
	 * @param subtask1
	 *            first subtask
	 * @param subtask2
	 *            second subtask
	 * @return true if an edge can be created, false otherwise
	 */
	public boolean canAddEdge(SubTask subtask1, SubTask subtask2) {
		for (SubTask parent : subtask1.getParentTasks()) {
			if (subtask2.getParentTasks().contains(parent))
				return false;
		}
		return true;
	}

	public void calculateCriticalPath() {
		initializeGraph();
		SubTask startSub = getStartingSubtask();
		startSub.setEarliestFinishTime(startSub.getWcet());
		calculateEarliestTime(startSub);
		SubTask endSub = getEndingSubtask();
		endSub.setLatestFinisheTime(endSub.getEarliestFinishTime());
		calculateLatestTime(endSub);
		for (SubTask sub : subtasks) {
			sub.setFirstRelease((sub.getEarliestFinishTime() - sub.getWcet()));
		}
	}

	public void parallelizeGraph() {
		boolean repeat;
		do {
			calculateCriticalPath();
			repeat = parallelizeSubtasks();
		} while (repeat);
	}

	/**
	 * Initializes the earliest and the latest time of all of the subtasks to
	 * finish their execution in the graph.
	 */
	private void initializeGraph() {
		for (SubTask subTask : subtasks) {
			subTask.setEarliestFinishTime(0);
			subTask.setLatestFinisheTime(0);
		}
	}

	/**
	 * Performs the earliest time for a subtask to finish its execution based on
	 * the depth-first algorithm. This is a recursive function used to perform
	 * forward calculations on all the subtasks of the graph, in order to find
	 * the critical path of a graph
	 * 
	 * @param subtask
	 *            current subtask in a graph
	 */
	private void calculateEarliestTime(SubTask subtask) {
		long earliestTime;
		if (subtask.isEndingSubtask())
			return;
		for (SubTask child : subtask.getChildrenTasks()) {

			// System.out.println(subtask+" : "+subtask.getEarliestFinishTime()+" + "+child.getWcet());
			earliestTime = subtask.getEarliestFinishTime() + child.getWcet();
			if (child.getEarliestFinishTime() < earliestTime)
				child.setEarliestFinishTime(earliestTime);
			calculateEarliestTime(child);
		}
	}

	/**
	 * Performs the latest time for a subtask to finish its execution based on
	 * the depth-first algorithm. This is a recursive function used to perform
	 * backward calculations on all the subtasks of the graph, in order to find
	 * the critical path of a graph
	 * 
	 * @param subtask
	 *            current subtask in a graph
	 */
	private void calculateLatestTime(SubTask subTask) {
		long latestTime;
		if (subTask.isStartingSubtask())
			return;
		for (SubTask parent : subTask.getParentTasks()) {
			latestTime = subTask.getLatestFinisheTime() - subTask.getWcet();
			if (parent.getLatestFinisheTime() == 0
					|| parent.getLatestFinisheTime() > latestTime)
				parent.setLatestFinisheTime(latestTime);
			calculateLatestTime(parent);
		}
	}

	/**
	 * Prints the earliest and the latest execution time of a subtask to finish
	 * in a graph
	 */
	public void printCriticalInfo() {
		for (SubTask subTask : subtasks) {
			System.out.println("[" + subTask.getId() + "]: wcet= "
					+ subTask.getWcet() + "  "
					+ subTask.getEarliestFinishTime() + "/"
					+ subTask.getLatestFinisheTime() + " --- nbProc: "
					+ subTask.getNumOfProc());
		}
	}

	private boolean parallelizeSubtasks() {
		boolean parallelized = false;
		CopyOnWriteArrayList<SubTask> modList = new CopyOnWriteArrayList<SubTask>();
		modList.addAll(subtasks);

		for (SubTask subTask : modList) {
			if (subTask.isCriticalTask() && subTask.getNumOfProc() > 1) {
				parallelized = true;
				for (int i = 0; i < subTask.getNumOfProc(); i++) {
					SubTask tmpSub = (SubTask) SchedulableFactory.newInstance(
							"subtask", subTask.getFirstRelease(),
							subTask.getWcet() / subTask.getNumOfProc(),
							subTask.getWcee(), subTask.getPeriod(),
							subTask.getDeadline(), subTask.getPriority(), 1);
					// SubTask tmpSub = new SubTask(subTask.getFirstRelease(),
					// subTask.getWcet() / subTask.getNumOfProc(),
					// subTask.getWcee(), subTask.getPeriod(),
					// subTask.getDeadline(), subTask.getPriority(), 1);
					tmpSub.setIndex(modList);
					modList.add(tmpSub);

					for (SubTask parent : subTask.getParentTasks()) {
						parent.getChildrenTasks().remove(subTask);
						addEdge(parent, tmpSub);
					}
					for (SubTask child : subTask.getChildrenTasks()) {
						child.getParentTasks().remove(subTask);
						addEdge(tmpSub, child);
					}

				}

				modList.remove(subTask);
			}
		}
		subtasks.clear();
		subtasks.addAll(modList);
		return parallelized;
	}

	public String toString() {
		return "["+getId()+"] wcet:" + getTotolWcet(subtasks) + " - nbSubTasks:"
				+ subtasks.size() + " - deadline: " + getDeadline()
				+ " - period: " + getPeriod();
	}

	@Override
	public ITask cloneTask() {
		return new GraphTask(this);
	}

	/**
	 * Accept function for the visitable element
	 */
	@Override
	public void accept(ITaskElementVisitor visitor) {
		visitor.visitGraphTask(this);
	}

	@Override
	public String getType() {
		return "graph";
	}

	/**
	 * Laxity of a graph is the difference between its deadline and its latest
	 * finish time after calculating the critical path
	 */
	@Override
	public long getLaxity() {
		calculateCriticalPath();
		long latestFinishOfGraph = getEndingSubtask().getLatestFinisheTime();
		if (latestFinishOfGraph == 0)
			throw new IllegalArgumentException(
					"Latest finish time of a graph is equal to zero!");
		return (getDeadline() - latestFinishOfGraph);
	}

	@Override
	public void activate(long time) {
		for (SubTask subtask : getSubtasks()) {
			//getActiveJobs().add(new Job(time, s, wcee, time + deadline));
			subtask.activate(time);

		}
	}
}